use ndarray::Array2;
use rstest::rstest;

use super::super::*;
use crate::tests::common::assert_expected_result_or_error;
use crate::tests::fixtures::{
    rand_usize,
    random_tile_type,
    RANDOM_BORDER_TILE_ID,
    RANDOM_MISSING_TILE_POLICY,
    RANDOM_STRINGS,
    RANDOM_TILES,
    RANDOM_TILE_SELECTION_POLICY,
    RANDOM_TILE_TYPE,
    RANDOM_USIZE,
    RANDOM_USIZES,
    RANDOM_VACANCY_POLICY,
};
use crate::tiling::tiles::ArrayInitializable;
use crate::Error;

#[rstest]
fn test_slot_grid_raw_initialization(
    #[values(None, Some(rand_usize(0, 12)))] border_tile_id: Option<usize>,
    #[values(MissingTilePolicy::Exit, MissingTilePolicy::Avoid)]
    missing_tile_policy: MissingTilePolicy,
    #[values(TileSelectionPolicy::Random, TileSelectionPolicy::Priority)]
    tile_selection_policy: TileSelectionPolicy,
    #[values(VacancyPolicy::Avoid, VacancyPolicy::Ignore)] vacancy_policy: VacancyPolicy,
    #[values(None, Some((rand_usize(0, 12), rand_usize(0, 13))))] current_predecessor: Option<(
        usize,
        usize,
    )>,
    #[values(
        vec![],
        vec![
            (rand_usize(0, 12), rand_usize(0, 13)),
            (rand_usize(0, 10), rand_usize(0, 14)),
            (rand_usize(0, 7), rand_usize(0, 8)),
        ],
    )]
    last_leaves: Vec<(usize, usize)>,
) {
    let grid = tests::create_test_grid(true);
    // The method clone() is not available due to unfulfilled constraints, so
    // duplicating grid is simpler than manually implementing the Clone trait.
    let grid_clone = tests::create_test_grid(true);
    let num_rows = 3;
    let num_cols = 2;
    let tile_list: TileList = TileList {
        tiles: RANDOM_TILES.iter().cloned().collect(),
        horizontal_connector_names: vec![],
        vertical_connector_names: vec![],
        tile_type: random_tile_type(),
    };
    let tile_connection_array = TileConnectionArray::new(&tile_list.tiles);
    // The method clone() is not available due to TileConnection’s lifetime
    // parameter, so calling TileConnectionArray::new() twice is simpler than
    // manually implementing the Clone trait.
    let tile_connection_array_clone = TileConnectionArray::new(&tile_list.tiles);
    let current_slot = (rand_usize(0, 13), rand_usize(0, 12));
    let exposed_edge_count = rand_usize(0, 3);

    let slot_grid: SlotGrid = SlotGrid {
        grid,
        num_rows,
        num_cols,
        border_tile_id,
        tile_list: tile_list.clone(),
        tile_connections: tile_connection_array,
        missing_tile_policy: missing_tile_policy.clone(),
        tile_selection_policy: tile_selection_policy.clone(),
        vacancy_policy: vacancy_policy.clone(),
        current_slot,
        current_predecessor,
        last_leaves: last_leaves.clone(),
        exposed_edge_count,
    };
    assert_eq!(slot_grid.grid, grid_clone);
    assert_eq!(slot_grid.num_rows, num_rows);
    assert_eq!(slot_grid.num_cols, num_cols);
    assert_eq!(slot_grid.border_tile_id, border_tile_id);
    assert_eq!(slot_grid.tile_list, tile_list);
    assert_eq!(slot_grid.tile_connections, tile_connection_array_clone);
    assert_eq!(slot_grid.missing_tile_policy, missing_tile_policy);
    assert_eq!(slot_grid.tile_selection_policy, tile_selection_policy);
    assert_eq!(slot_grid.vacancy_policy, vacancy_policy);
    assert_eq!(slot_grid.current_slot, current_slot);
    assert_eq!(slot_grid.current_predecessor, current_predecessor);
    assert_eq!(slot_grid.last_leaves, last_leaves);
}

#[rstest]
#[case::zero_rows(
    0,
    RANDOM_USIZES[0],
    TileList {
        tiles: RANDOM_TILES.iter().cloned().collect(),
        horizontal_connector_names: vec!["🃟".to_string()],
        vertical_connector_names: vec!["🃟".to_string()],
        tile_type: random_tile_type(),
    },
    None,
    None,
    Some(Error::ValueError(format!(
        "num_rows and num_cols must be > 0, got num_rows = 0 and num_cols = {}",
        RANDOM_USIZES[0],
    ))),
)]
#[case::zero_cols(
    RANDOM_USIZES[1],
    0,
    TileList {
        tiles: RANDOM_TILES.iter().cloned().collect(),
        horizontal_connector_names: vec!["🃟".to_string()],
        vertical_connector_names: vec![],
        tile_type: random_tile_type(),
    },
    Some(*RANDOM_USIZE + 4),
    None,
    Some(Error::ValueError(format!(
        "num_rows and num_cols must be > 0, got num_rows = {} and num_cols = 0",
        RANDOM_USIZES[1],
    ))),
)]
#[case::n_rows_m_cols(
    RANDOM_USIZES[0] + 1,
    RANDOM_USIZES[1] + 1,
    TileList {
        tiles: RANDOM_TILES.iter().cloned().collect(),
        horizontal_connector_names: vec![],
        vertical_connector_names: vec![RANDOM_STRINGS[6].clone()],
        tile_type: RANDOM_TILE_TYPE.clone(),
    },
    *RANDOM_BORDER_TILE_ID,
    Some(SlotGrid {
        grid: Array2::default((
            RANDOM_USIZES[0] + 2, RANDOM_USIZES[1] + 2
        )),
        tile_list: TileList {
            tiles: RANDOM_TILES.iter().cloned().collect(),
            horizontal_connector_names: vec![],
            vertical_connector_names: vec![RANDOM_STRINGS[6].clone()],
            tile_type: RANDOM_TILE_TYPE.clone(),
        },
        tile_connections: TileConnectionArray::new(&tile_list.tiles),
        border_tile_id: *RANDOM_BORDER_TILE_ID,
        missing_tile_policy: RANDOM_MISSING_TILE_POLICY.clone(),
        tile_selection_policy: RANDOM_TILE_SELECTION_POLICY.clone(),
        vacancy_policy: RANDOM_VACANCY_POLICY.clone(),
        num_rows: RANDOM_USIZES[0] + 1,
        num_cols: RANDOM_USIZES[1] + 1,
        current_slot: (
            (RANDOM_USIZES[0] + 2) / 2,
            (RANDOM_USIZES[1] + 2) / 2,
        ),
        current_predecessor: None,
        last_leaves: vec![],
        exposed_edge_count: 0,
    }),
    None,
)]
#[case::invalid_border_tile_id(
    *RANDOM_USIZE + 1,
    *RANDOM_USIZE + 2,
    TileList {
        tiles: RANDOM_TILES.iter().cloned().collect(),
        horizontal_connector_names: vec![],
        vertical_connector_names: vec![],
        tile_type: random_tile_type(),
    },
    Some(*RANDOM_USIZE + 50),
    None,
    Some(Error::ValueError(
        format!("Invalid border tile ID: {}", *RANDOM_USIZE + 50)
    )),
)]
fn test_slot_grid_new(
    #[case] num_rows: usize,
    #[case] num_cols: usize,
    #[case] tile_list: TileList,
    #[case] border_tile_id: Option<usize>,
    #[case] expected_slot_grid: Option<SlotGrid>,
    #[case] expected_error: Option<Error>,
) {
    let result = SlotGrid::new(
        num_rows,
        num_cols,
        tile_list,
        border_tile_id,
        RANDOM_MISSING_TILE_POLICY.clone(),
        RANDOM_TILE_SELECTION_POLICY.clone(),
        RANDOM_VACANCY_POLICY.clone(),
    );

    assert_expected_result_or_error(result, expected_slot_grid, expected_error);
}
